package gbase8s

import (
	"database/sql"
	"errors"
	"fmt"
	"strings"

	"gorm.io/gorm"
	"gorm.io/gorm/clause"
	"gorm.io/gorm/migrator"
	"gorm.io/gorm/schema"
)

type Migrator struct {
	migrator.Migrator
}

func (m *Migrator) RunWithoutForeignKey(fc func() error) error {
	m.DB.Exec("SET CONSTRAINTS ALL DEFERRED")
	defer m.DB.Exec("SET CONSTRAINTS ALL IMMEDIATE")

	return fc()
}

func (m Migrator) CreateTable(values ...interface{}) error {
	//return m.Migrator.CreateTable(values...)
	for _, value := range m.ReorderModels(values, false) {
		tx := m.DB.Session(&gorm.Session{})
		if err := m.RunWithValue(value, func(stmt *gorm.Statement) (err error) {

			if stmt.Schema == nil {
				return errors.New("failed to get schema")
			}

			var (
				createTableSQL          = "CREATE TABLE ? ("
				values                  = []interface{}{m.CurrentTable(stmt)}
				hasPrimaryKeyInDataType bool
			)

			for _, dbName := range stmt.Schema.DBNames {
				field := stmt.Schema.FieldsByDBName[dbName]
				if !field.IgnoreMigration {
					createTableSQL += "? ?"
					hasPrimaryKeyInDataType = hasPrimaryKeyInDataType || strings.Contains(strings.ToUpper(m.DataTypeOf(field)), "PRIMARY KEY")
					values = append(values, clause.Column{Name: dbName}, m.DB.Migrator().FullDataTypeOf(field))
					createTableSQL += ","
				}
			}

			if !hasPrimaryKeyInDataType && len(stmt.Schema.PrimaryFields) > 0 {
				createTableSQL += "PRIMARY KEY ?,"
				primaryKeys := make([]interface{}, 0, len(stmt.Schema.PrimaryFields))
				for _, field := range stmt.Schema.PrimaryFields {
					primaryKeys = append(primaryKeys, clause.Column{Name: field.DBName})
				}

				values = append(values, primaryKeys)
			}

			// add unique
			for _, uni := range stmt.Schema.ParseUniqueConstraints() {
				createTableSQL += "UNIQUE (?)"
				values = append(values, clause.Expr{SQL: stmt.Quote(uni.Field.DBName)})
			}

			for _, idx := range stmt.Schema.ParseIndexes() {
				if m.CreateIndexAfterCreateTable {
					defer func(value interface{}, name string) {
						if err == nil {
							err = tx.Migrator().CreateIndex(value, name)
						}
					}(value, idx.Name)
				} else {
					if idx.Class != "" {
						createTableSQL += idx.Class + " "
					}
					createTableSQL += "INDEX ? ?"

					if idx.Comment != "" {
						createTableSQL += fmt.Sprintf(" COMMENT '%s'", idx.Comment)
					}

					if idx.Option != "" {
						createTableSQL += " " + idx.Option
					}

					createTableSQL += ","
					values = append(values, clause.Column{Name: idx.Name}, tx.Migrator().(migrator.BuildIndexOptionsInterface).BuildIndexOptions(idx.Fields, stmt))
				}
			}

			if !m.DB.DisableForeignKeyConstraintWhenMigrating && !m.DB.IgnoreRelationshipsWhenMigrating {
				type fkContent struct {
					fk    string
					table string
					col   string
				}
				fks := make(map[fkContent]bool)
				for _, rel := range stmt.Schema.Relationships.Relations {
					if rel.Field.IgnoreMigration {
						continue
					}
					if constraint := rel.ParseConstraint(); constraint != nil {
						if constraint.Schema == stmt.Schema {
							// 删除相同的约束
							var fk fkContent
							fk.fk = constraint.ForeignKeys[0].DBName
							fk.table = constraint.References[0].Schema.Table
							fk.col = constraint.References[0].DBName
							if _, ok := fks[fk]; ok {
								continue
							} else {
								fks[fk] = true
							}
						}
					}
				}
			}

			//for _, uni := range stmt.Schema.ParseUniqueConstraints() {
			//	createTableSQL += "CONSTRAINT ? UNIQUE (?),"
			//	values = append(values, clause.Column{ColunmName: uni.ColunmName}, clause.Expr{SQL: stmt.Quote(uni.Field.DBName)})
			//}

			for _, chk := range stmt.Schema.ParseCheckConstraints() {
				createTableSQL += "CONSTRAINT ? CHECK (?),"
				values = append(values, clause.Column{Name: chk.Name}, clause.Expr{SQL: chk.Constraint})
			}

			createTableSQL = strings.TrimSuffix(createTableSQL, ",")

			createTableSQL += ")"

			if tableOption, ok := m.DB.Get("gorm:table_options"); ok {
				createTableSQL += fmt.Sprint(tableOption)
			}

			err = tx.Exec(createTableSQL, values...).Error
			return err
		}); err != nil {
			return err
		}
	}
	return nil
}

func (m Migrator) DropTable(dst ...interface{}) error {
	dst = m.ReorderModels(dst, false)
	for i := len(dst) - 1; i >= 0; i-- {
		value := dst[i]
		tx := m.DB.Session(&gorm.Session{})
		if m.HasTable(value) {
			if err := m.RunWithValue(value, func(stmt *gorm.Statement) error {
				return tx.Exec("DROP TABLE ? CASCADE CONSTRAINTS", clause.Table{Name: stmt.Table}).Error
			}); err != nil {
				return err
			}
		}
	}
	return nil
	return m.RunWithoutForeignKey(func() error {
		return m.Migrator.DropTable(dst...)
	})
	// TODO implement me
	panic("implement me")
}

func (m Migrator) HasTable(dst interface{}) bool {
	var count int = 0
	m.Migrator.RunWithValue(dst, func(stmt *gorm.Statement) error {
		return m.DB.Raw("SELECT count(*) FROM systables WHERE tabname = ?", stmt.Table).Row().Scan(&count)
	})
	return count > 0
}

func (m Migrator) RenameTable(oldName, newName interface{}) error {
	// TODO implement me
	panic("implement me")
}

func (m Migrator) GetTables() (tableList []string, err error) {
	err = m.DB.Raw(`SELECT tabname FROM systables`).Scan(&tableList).Error
	return
}

func (m Migrator) TableType(dst interface{}) (gorm.TableType, error) {
	// TODO implement me
	panic("implement me")
}

func (m Migrator) CurrentDatabase() (name string) {
	m.DB.Raw("select trim(odb_dbname) from sysmaster:sysopendb where odb_sessionid=dbinfo('sessionid') and odb_iscurrent='Y';").
		Scan(&name)

	return
}

func (m Migrator) FullDataTypeOf(field *schema.Field) (expr clause.Expr) {
	expr.SQL = m.DataTypeOf(field)

	if field.HasDefaultValue && (field.DefaultValueInterface != nil || field.DefaultValue != "") {
		if field.DefaultValueInterface != nil {
			defaultStmt := &gorm.Statement{Vars: []interface{}{field.DefaultValueInterface}}
			m.Dialector.BindVarTo(defaultStmt, defaultStmt, field.DefaultValueInterface)
			expr.SQL += " DEFAULT " + m.Dialector.Explain(defaultStmt.SQL.String(), field.DefaultValueInterface)
		} else if field.DefaultValue != "(-)" {
			expr.SQL += " DEFAULT " + field.DefaultValue
		}
	}

	if field.NotNull {
		expr.SQL += " NOT NULL"
	}

	return
}

func (m Migrator) MigrateColumn(dst interface{}, field *schema.Field, columnType gorm.ColumnType) error {
	return m.Migrator.MigrateColumn(dst, field, columnType)
}

func (m Migrator) MigrateColumnUnique(dst interface{}, field *schema.Field, columnType gorm.ColumnType) error {
	return m.Migrator.MigrateColumnUnique(dst, field, columnType)
}

func (m Migrator) ColumnTypes(dst interface{}) ([]gorm.ColumnType, error) {
	log.Info("ColumnTypes", Any("dst", dst), Stack("stack"))
	columnTypes := make([]gorm.ColumnType, 0)
	execErr := m.RunWithValue(dst, func(stmt *gorm.Statement) (err error) {
		_, tableName := m.getSchemaTable(stmt)
		colsSql := "select colno, coltype, collength, extended_id from syscolumns where tabid = (select tabid from systables where tabname = ?)"
		colsSql = "SELECT column_name, data_type, data_length, nullable  FROM user_tab_columns WHERE table_name = ?"
		//colsSql = "SELECT count(*) FROM user_tab_columns WHERE table_name = ?"
		tx := m.DB.Raw(colsSql, strings.ToUpper(tableName))
		if tx.Error != nil {
			return tx.Error
		}

		type cols struct {
			ColunmName string
			DataType   string
			DataLength int64
			Nullable   bool
		}

		//var ccc cols
		//tx.First(&ccc)

		rows, err := tx.Rows()
		if err != nil {
			return err
		}

		defer func() {
			err = rows.Close()
		}()

		for rows.Next() {
			var col cols
			var nullable string
			err = rows.Scan(&col.ColunmName, &col.DataType, &col.DataLength, &nullable)
			if err != nil {
				return err
			}

			switch nullable {
			case "Y":
				col.Nullable = true
			}

			columnType := migrator.ColumnType{
				NameValue:        sql.NullString{String: strings.ToLower(col.ColunmName), Valid: true},
				DataTypeValue:    sql.NullString{String: strings.ToLower(col.DataType), Valid: true},
				LengthValue:      sql.NullInt64{Int64: int64(col.DataLength), Valid: true},
				NullableValue:    sql.NullBool{Bool: col.Nullable, Valid: true},
				DecimalSizeValue: sql.NullInt64{Int64: 0, Valid: true},
			}
			columnTypes = append(columnTypes, columnType)
		}

		return
	})

	return columnTypes, execErr
}

func (m Migrator) CreateView(name string, option gorm.ViewOption) error {
	// TODO implement me
	panic("implement me")
}

func (m Migrator) GetIndexes(dst interface{}) ([]gorm.Index, error) {
	// TODO implement me
	panic("implement me")
}

func (m Migrator) AlterColumn(value interface{}, field string) error {
	return m.RunWithValue(value, func(stmt *gorm.Statement) error {
		if stmt.Schema != nil {
			if field := stmt.Schema.LookUpField(field); field != nil {
				fileType := m.FullDataTypeOf(field)
				return m.DB.Exec(
					"ALTER TABLE ? MODIFY (? ?)",
					m.CurrentTable(stmt), clause.Column{Name: field.DBName}, fileType,
				).Error

			}
		}
		return fmt.Errorf("failed to look up field with name: %s", field)
	})
}

func (m Migrator) HasConstraint(value interface{}, name string) bool {
	var count int64
	return m.RunWithValue(value, func(stmt *gorm.Statement) error {
		return m.DB.Raw(
			"SELECT COUNT(*) FROM USER_CONSTRAINTS WHERE TABLE_NAME = ? AND CONSTRAINT_NAME = ?",
			stmt.Table,
			name,
		).Row().Scan(&count)
	}) == nil && count > 0
}

func (m Migrator) HasIndex(value interface{}, name string) bool {
	var count int64
	_ = m.RunWithValue(value, func(stmt *gorm.Statement) error {
		if idx := stmt.Schema.LookIndex(name); idx != nil {
			name = idx.Name
		}

		return m.DB.Raw(
			"SELECT COUNT(*) FROM USER_INDEXES WHERE TABLE_NAME = ? AND INDEX_NAME = ?",
			strings.ToUpper(m.Migrator.DB.NamingStrategy.TableName(stmt.Table)),
			strings.ToUpper(name),
		).Row().Scan(&count)
	})

	return count > 0
}

func (m Migrator) DropIndex(value interface{}, name string) error {
	return m.RunWithValue(value, func(stmt *gorm.Statement) error {
		if idx := stmt.Schema.LookIndex(name); idx != nil {
			name = idx.Name
		}
		//_, tableName := m.getSchemaTable(stmt)

		return m.DB.Exec("DROP INDEX ?", clause.Column{Name: name}).Error
	})
}

func (m Migrator) RenameIndex(value interface{}, oldName, newName string) error {
	return errors.New("unsupported rename index")
}

func (m Migrator) getSchemaTable(stmt *gorm.Statement) (ownerName, tableName string) {
	if stmt == nil {
		return
	}
	if stmt.Schema == nil {
		tableName = stmt.Table
	} else {
		tableName = stmt.Schema.Table
		if strings.Contains(tableName, ".") {
			ownerTable := strings.Split(tableName, ".")
			ownerName, tableName = ownerTable[0], ownerTable[1]
		}
	}
	return
}
